package multitester.annonations.processors;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;

/**
 * @author Yuriy Sechko
 */
public class VoidMethodPointer
{
	private final Object instance;
	private final Method method;

	public VoidMethodPointer(final Object instance, final Method method)
	{
		Checkings.instanceIsNull(instance);
		Checkings.methodIsNull(method);
		Checkings.methodIsStatic(method);
		Checkings.isReturnTypeAcceptable(method);
		Checkings.areThereSomeArguments(method);
		Checkings.areMethodAndInstanceTypeInTheSameHierarchy(instance, method);
		this.instance = instance;
		this.method = method;
	}

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        VoidMethodPointer pointer = (VoidMethodPointer) o;

        if (instance != null ? !instance.equals(pointer.instance) : pointer.instance != null) return false;
        if (method != null ? !method.equals(pointer.method) : pointer.method != null) return false;

        return true;
    }

    @Override
    public int hashCode() {
        int result = instance != null ? instance.hashCode() : 0;
        result = 31 * result + (method != null ? method.hashCode() : 0);
        return result;
    }

    public void invoke()
	{
		try
		{
			method.setAccessible(true);
			method.invoke(instance);
		}
		catch (IllegalAccessException accessException)
		{
			throw new IllegalStateException(composeErrorMessage(instance,
					method), accessException);
		}
		catch (InvocationTargetException invocationException)
		{
			throw new IllegalStateException(composeErrorMessage(instance,
					method), invocationException);
		}
	}

	private static String composeErrorMessage(final Object instance,
			final Method method)
	{
		return String.format(
				"Cannot invoke method %s on %s instance via reflection.",
				method.getName(), instance.getClass().getCanonicalName());
	}

	private static class Checkings
	{
		// Only 'void' and 'Void' return type accepted
		private static final Set<Class> acceptableReturnTypes;
		static
		{
			final Set<Class> tempSet = new HashSet<Class>();
			tempSet.add(Void.TYPE);
			tempSet.add(Void.class);
			acceptableReturnTypes = Collections.unmodifiableSet(tempSet);
		}

		private static void methodIsStatic(final Method method)
		{
			if ((method.getModifiers() & Modifier.STATIC) != 0)
			{
				throw new IllegalArgumentException(
						"Method should not be static.");
			}
		}

		private static void methodIsNull(final Method method)
		{
			if (method == null)
			{
				throw new IllegalArgumentException(
						"Method placeholder should not be null.");
			}
		}

		private static void instanceIsNull(final Object instance)
		{
			if (instance == null)
			{
				throw new IllegalArgumentException(
						"Instance for calling method should not be null.");
			}
		}

		private static void isReturnTypeAcceptable(final Method method)
		{
			final Class methodReturnType = method.getReturnType();
			if (!acceptableReturnTypes.contains(methodReturnType))
			{
				final String message = String
						.format("Method should return 'void' or 'Void', but returns '%s'",
								methodReturnType.getCanonicalName());
				throw new IllegalArgumentException(message);
			}
		}

		private static void areThereSomeArguments(Method method)
		{
			if (method.getParameterTypes().length != 0)
			{
				throw new IllegalArgumentException(
						"Method should not take any arguments.");
			}
		}

		private static void areMethodAndInstanceTypeInTheSameHierarchy(
				final Object instance, final Method method)
		{
			if (!method.getDeclaringClass().isAssignableFrom(
					instance.getClass()))
			{
				throw new IllegalArgumentException(
						"Method's declared class should be equal to the instance class.");
			}
		}
	}
}
